#include "Fraction.h"

#include <cassert>
#include <cstdlib>
#include <sstream>
#include <cmath>
#include <stdexcept>

namespace math {

Fraction::Fraction(long long numerator, long long denominator)
    : m_numerator(std::abs(numerator))
    , m_denominator(std::abs(denominator))
    , m_minus((numerator < 0) ^ (denominator < 0)) {
    assert(m_denominator != 0);
    
    m_reduct();
}

Fraction::Fraction(std::string x) {
    try {
        std::stold(x);
    } catch(std::exception e) {
        std::stringstream msg;
        msg << "The argument \"std::string x\"(" << x << ") is invalid";
        throw std::runtime_error(msg.str());
    }
    
    std::stringstream number;
    int digits = 0;
    
    bool flag = false;
    for (int i = 0; i < x.size(); ++i) {
        const auto& c = x[i];
        
        if (i == 0) {
            m_minus = (c == '-');
        }
        
        if (c == '.') {
            flag = true;
            continue;
        }
        
        number << c;
        
        if (flag) {
            ++digits;
        }
    }
    
    m_numerator = std::stol(number.str());
    m_denominator = std::pow(10, digits);
    
    m_reduct();
}

Fraction::Fraction(Fraction& frac)
    : Fraction(frac.m_numerator, frac.m_denominator) {}

void Fraction::m_reduct() {
    auto tmp = gcd(m_numerator, m_denominator);
    
    m_numerator /= tmp;
    m_denominator /= tmp;
}

Fraction& Fraction::operator-() const {
    return *(new Fraction((m_minus ? 1 : -1) * m_numerator, m_denominator));
}

Fraction& Fraction::operator~() const {
    return *(new Fraction((m_minus ? -1 : 1) * m_denominator, m_numerator));
}

Fraction& Fraction::operator+(const Fraction& frac) const {
    return *(new Fraction(
        (frac.m_minus ? -1 : 1) * frac.m_numerator * m_denominator +
        (m_minus ? -1 : 1) * frac.m_denominator * m_numerator,
        frac.m_denominator * m_denominator
    ));
}

Fraction& Fraction::operator-(const Fraction& frac) const {
    return (*this) + (-frac);
}

Fraction& Fraction::operator*(const Fraction& frac) const {
    return *(new Fraction(
        ((m_minus ^ frac.m_minus) ? -1 : 1) * m_numerator * frac.m_numerator,
        m_denominator * frac.m_denominator
    ));
}

Fraction& Fraction::operator/(const Fraction& frac) const {
    return (*this) * (~frac);
}

long long Fraction::getNumerator() const {
    return m_numerator;
}

long long Fraction::getDenominator() const {
    return m_denominator;
}

bool Fraction::getMinus() const {
    return m_minus;
}

float Fraction::toFloat() const {
    return static_cast<float>(toLDouble());
}

double Fraction::toDouble() const {
    return static_cast<double>(toLDouble());
}

long double Fraction::toLDouble() const {
    return (m_minus ? -1.0f : 1.0f) * m_numerator / m_denominator;
}

std::ostream& operator<<(std::ostream& os, const Fraction& frac) {
    if (frac.m_minus) {
        os << '-';
    }
    os << frac.m_numerator << "/" << frac.m_denominator;
    return os;
}

};
